import { CydonElement, context, define } from 'cydon'
import { API } from '../api'
import { debounce } from '../utils'

/** 最小字符数 */
const minChars = 2

/** 建议数量 */
const COUNT =
	navigator.hardwareConcurrency > 1 && navigator.deviceMemory > 2 ? 20 : 10

/** 获取完成文本 */
function getCompletionText(input: HTMLInputElement) {
	const pos = input.value.lastIndexOf(' ') + 1
	return input.value.substring(pos, input.selectionStart!)
}

type AttrName = (typeof CAutoComplete.observedAttributes)[number]

@define('c-auto-complete')
export class CAutoComplete extends CydonElement {
	static observedAttributes = ['for', 'key'] as const
	/** 标签列表 */
	tags: string[] = []

	/** 上一次输入的值 */
	lastVal = ''

	/** 正在输入的文本 */
	text = ''
	/** 输入框 */
	input!: HTMLInputElement

	/** 键 */
	key!: string

	/** 标签列表 */
	list!: HTMLElement

	resizeHandler!: () => void

	async connectedCallback() {
		this.innerHTML = emt`.auto-complete.show-order
		template[c-for="tag; tags"]
			tag-item[.name=tag]
				a[href=# highlight=name]
					background $bgColor
					border-top $borderTop
					border-left $borderLeft
					border-bottom $borderBottom
					border-right $borderRight
					color $color
					font-size $fontSize
				span.ml-2{$num}
					color $countColor`
		this.list = this.querySelector('.auto-complete')!
		await customElements.whenDefined('tag-item')
		super.connectedCallback()
		this.input ??= this.querySelector('input')!
		// 点击建议填充到输入框
		// TODO: 显示标签别名
		this.addEventListener('click', e => {
			let node = e.target as HTMLAnchorElement
			while (node && node.tagName != 'A')
				node = node.parentElement as HTMLAnchorElement
			if (node?.href) {
				e.stopPropagation()
				e.preventDefault()
				let val = this.input.value
				const pos = val.lastIndexOf(' ') + 1
				val = val.substring(0, pos) + node.innerText
				this.input[context][this.key] = val.trimStart()
			}
		})
	}

	attributeChangedCallback(name: AttrName, _oldVal: string, newVal: string) {
		if (name == 'for') {
			if (this.input)
				this.disconnectedCallback()
			this.input = document.getElementById(newVal) as HTMLInputElement
			if (!this.input)
				throw new Error(`Element #${newVal} not found`)
			this.bindEvents()
		} else if (name == 'key') {
			this.key = newVal
		}
	}

	/** 解绑事件 */
	disconnectedCallback() {
		removeEventListener('resize', this.resizeHandler)
		const input = this.input
		input.removeEventListener('blur', this.blurHandler)
		input.removeEventListener('keydown', this.keydownHandler)
		input.removeEventListener('keyup', this.keyupHandler)
	}

	/** 绑定事件 */
	bindEvents() {
		this.resizeHandler = () => this.onResize()
		addEventListener('resize', this.resizeHandler)
		const input = this.input
		input.addEventListener('blur', this.blurHandler)
		input.addEventListener('keydown', this.keydownHandler)
		input.addEventListener('keyup', this.keyupHandler)
	}

	private onResize() {
		const { input, style } = this
		style.left = input.offsetLeft + 'px'
		style.top = input.offsetTop + input.offsetHeight + 'px'
		style.width = input.offsetWidth + 'px'
	}

	blurHandler = () => {
		const input = this.input
		const hover = this.querySelector('.auto-complete:hover')
		if (!hover) {
			this.lastVal = input.value
			// TODO: 添加渐变效果
			// hide suggestions on fast input
			setTimeout(() => this.hideSuggestions(), 350)
		} else if (input != document.activeElement)
			setTimeout(() => input.focus(), 20)
	}

	/** 隐藏建议 */
	hideSuggestions() {
		this.list.style.display = 'none'
	}

	keydownHandler = (e: KeyboardEvent) => {
		const key = e.key
		if (key == 'ArrowDown' || key == 'ArrowUp') {
			e.preventDefault()
			const hover = this.querySelector('.active')
			if (hover) {
				const next = key == 'ArrowUp' ? hover.previousElementSibling : hover.nextElementSibling
				if (next) {
					hover.classList.remove('active')
					next.classList.add('active')
				}
			} else {
				const first = this.querySelector(
					`tag-item:${key == 'ArrowUp' ? 'last' : 'first'}-of-type`)
				if (first)
					first.classList.add('active')
			}
		} else if (key == 'Enter' && this.tags.length) {
			const hover = this.querySelector('.active')
			if (hover) {
				const val = this.input.value
				const pos = val.lastIndexOf(' ') + 1
				this.input[context][this.key] = val.substring(0, pos) + hover.firstChild!.textContent
				this.hideSuggestions()
			}
		}
	}

	keyupHandler = (e: KeyboardEvent) => {
		const key = e.key
		if (key != 'ArrowDown' && key != 'ArrowUp' && key != 'Enter') {
			debounce(() => this.updateSuggestions())()
		}
	}

	private updateSuggestions() {
		const value = getCompletionText(this.input)
		if (value.length >= minChars || value.codePointAt(0)! > 127) {
			API.autocmpl(value, "", COUNT).then(tags => {
				this.list.style.display = 'block'
				this.text = value
				this.data.tags = tags
			})
		} else {
			this.hideSuggestions()
		}
	}
}

declare global {
	interface HTMLElementTagNameMap {
		'c-auto-complete': CAutoComplete
	}

	interface Navigator {
		deviceMemory: number
	}
}